home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / daemons / ftpd / ftpcmd.y < prev    next >
Text File  |  1992-08-11  |  17KB  |  928 lines

  1. /*
  2.  * Copyright (c) 1985 Regents of the University of California.
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms are permitted
  6.  * provided that this notice is preserved and that due credit is given
  7.  * to the University of California at Berkeley. The name of the University
  8.  * may not be used to endorse or promote products derived from this
  9.  * software without specific prior written permission. This software
  10.  * is provided ``as is'' without express or implied warranty.
  11.  */
  12.  
  13. /*
  14.  * Grammar for FTP commands.
  15.  * See RFC 765.
  16.  */
  17.  
  18. %{
  19.  
  20. #ifndef lint
  21. static char sccsid[] = "@(#)ftpcmd.y    5.10 (Berkeley) 3/14/88";
  22. #endif /* not lint */
  23.  
  24. #include <sys/types.h>
  25. #include <sys/socket.h>
  26.  
  27. #include <netinet/in.h>
  28.  
  29. #include <arpa/ftp.h>
  30.  
  31. #include <stdio.h>
  32. #include <signal.h>
  33. #include <ctype.h>
  34. #include <pwd.h>
  35. #include <setjmp.h>
  36. #include <syslog.h>
  37.  
  38. extern    struct sockaddr_in data_dest;
  39. extern    int logged_in;
  40. extern    struct passwd *pw;
  41. extern    int guest;
  42. extern    int logging;
  43. extern    int type;
  44. extern    int form;
  45. extern    int debug;
  46. extern    int timeout;
  47. extern  int pdata;
  48. extern    char hostname[];
  49. extern    char *globerr;
  50. extern    int usedefault;
  51. extern    int unique;
  52. extern  int transflag;
  53. extern  char tmpline[];
  54. char    **glob();
  55. extern    int guestok();
  56.  
  57. static    int cmd_type;
  58. static    int cmd_form;
  59. static    int cmd_bytesz;
  60. char cbuf[512];
  61. char *fromname;
  62.  
  63. char    *index();
  64. %}
  65.  
  66. %token
  67.     A    B    C    E    F    I
  68.     L    N    P    R    S    T
  69.  
  70.     SP    CRLF    COMMA    STRING    NUMBER
  71.  
  72.     USER    PASS    ACCT    REIN    QUIT    PORT
  73.     PASV    TYPE    STRU    MODE    RETR    STOR
  74.     APPE    MLFL    MAIL    MSND    MSOM    MSAM
  75.     MRSQ    MRCP    ALLO    REST    RNFR    RNTO
  76.     ABOR    DELE    CWD    LIST    NLST    SITE
  77.     STAT    HELP    NOOP    XMKD    XRMD    XPWD
  78.     XCUP    STOU
  79.  
  80.     LEXERR
  81.  
  82. %start    cmd_list
  83.  
  84. %%
  85.  
  86. cmd_list:    /* empty */
  87.     |    cmd_list cmd
  88.         = {
  89.             fromname = (char *) 0;
  90.         }
  91.     |    cmd_list rcmd
  92.     ;
  93.  
  94. cmd:        USER SP username CRLF
  95.         = {
  96.             extern struct passwd *getpwnam();
  97.  
  98.             logged_in = 0;
  99.             if (strcmp((char *) $3, "ftp") == 0 ||
  100.               strcmp((char *) $3, "anonymous") == 0) {
  101.                 if ((pw = getpwnam("ftp")) != NULL) {
  102.                     guest = 1;
  103.                     reply(331,
  104.                   "Guest login ok; supply userid as password.");
  105.                 }
  106.                 else {
  107.                     reply(530, "User %s unknown.", $3);
  108.                 }
  109.             } else if (checkuser((char *) $3)) {
  110.                 guest = 0;
  111.                 pw = getpwnam((char *) $3);
  112.                 if (pw == NULL) {
  113.                     reply(530, "User %s unknown.", $3);
  114.                 }
  115.                 else {
  116.                     reply(331, "Password required for %s.", $3);
  117.                 }
  118.             } else {
  119.                 reply(530, "User %s access denied.", $3);
  120.             }
  121.             free((char *) $3);
  122.         }
  123.     |    PASS SP password CRLF
  124.         = {
  125.             pass((char *) $3);
  126.             free((char *) $3);
  127.         }
  128.     |    PORT SP host_port CRLF
  129.         = {
  130.             usedefault = 0;
  131.             if (pdata > 0) {
  132.                 (void) close(pdata);
  133.             }
  134.             pdata = -1;
  135.             reply(200, "PORT command successful.");
  136.         }
  137.     |    PASV CRLF
  138.         = {
  139.             passive();
  140.         }
  141.     |    TYPE SP type_code CRLF
  142.         = {
  143.             switch (cmd_type) {
  144.  
  145.             case TYPE_A:
  146.                 if (cmd_form == FORM_N) {
  147.                     reply(200, "Type set to A.");
  148.                     type = cmd_type;
  149.                     form = cmd_form;
  150.                 } else
  151.                     reply(504, "Form must be N.");
  152.                 break;
  153.  
  154.             case TYPE_E:
  155.                 reply(504, "Type E not implemented.");
  156.                 break;
  157.  
  158.             case TYPE_I:
  159.                 reply(200, "Type set to I.");
  160.                 type = cmd_type;
  161.                 break;
  162.  
  163.             case TYPE_L:
  164.                 if (cmd_bytesz == 8) {
  165.                     reply(200,
  166.                         "Type set to L (byte size 8).");
  167.                     type = cmd_type;
  168.                 } else
  169.                     reply(504, "Byte size must be 8.");
  170.             }
  171.         }
  172.     |    STRU SP struct_code CRLF
  173.         = {
  174.             switch ($3) {
  175.  
  176.             case STRU_F:
  177.                 reply(200, "STRU F ok.");
  178.                 break;
  179.  
  180.             default:
  181.                 reply(504, "Unimplemented STRU type.");
  182.             }
  183.         }
  184.     |    MODE SP mode_code CRLF
  185.         = {
  186.             switch ($3) {
  187.  
  188.             case MODE_S:
  189.                 reply(200, "MODE S ok.");
  190.                 break;
  191.  
  192.             default:
  193.                 reply(502, "Unimplemented MODE type.");
  194.             }
  195.         }
  196.     |    ALLO SP NUMBER CRLF
  197.         = {
  198.             reply(202, "ALLO command ignored.");
  199.         }
  200.     |    RETR check_login SP pathname_perm CRLF
  201.         = {
  202.             if ($2 && $4 != NULL)
  203.                 retrieve((char *) 0, (char *) $4);
  204.             if ($4 != NULL)
  205.                 free((char *) $4);
  206.         }
  207. /*
  208.  * To allow guests to put files, change check_login_guest to check_login.
  209.  */
  210.     |    STOR check_login_guest SP pathname_perm CRLF
  211.         = {
  212.             if ($2 && $4 != NULL)
  213.                 store((char *) $4, "w");
  214.             if ($4 != NULL)
  215.                 free((char *) $4);
  216.         }
  217.     |    APPE check_login_guest SP pathname_perm CRLF
  218.         = {
  219.             if ($2 && $4 != NULL)
  220.                 store((char *) $4, "a");
  221.             if ($4 != NULL)
  222.                 free((char *) $4);
  223.         }
  224.     |    NLST check_login CRLF
  225.         = {
  226.             if ($2)
  227.                 retrieve("/bin/ls", "");
  228.         }
  229.     |    NLST check_login SP pathname_perm CRLF
  230.         = {
  231.             if ($2 && $4 != NULL)
  232.                 retrieve("/bin/ls %s", (char *) $4);
  233.             if ($4 != NULL)
  234.                 free((char *) $4);
  235.         }
  236.     |    LIST check_login CRLF
  237.         = {
  238.             if ($2)
  239.                 retrieve("/bin/ls -lLg", "");
  240.         }
  241.     |    LIST check_login SP pathname_perm CRLF
  242.         = {
  243.             if ($2 && $4 != NULL)
  244.                 retrieve("/bin/ls -lLg %s", (char *) $4);
  245.             if ($4 != NULL)
  246.                 free((char *) $4);
  247.         }
  248.     |    DELE check_login_guest SP pathname_perm CRLF
  249.         = {
  250.             if ($2 && $4 != NULL)
  251.                 delete((char *) $4);
  252.             if ($4 != NULL)
  253.                 free((char *) $4);
  254.         }
  255.     |    RNTO SP pathname_perm CRLF
  256.         = {
  257.             if (fromname) {
  258.                 renamecmd(fromname, (char *) $3);
  259.                 free(fromname);
  260.                 fromname = (char *) 0;
  261.             } else {
  262.                 reply(503, "Bad sequence of commands.");
  263.             }
  264.             free((char *) $3);
  265.         }
  266.     |    ABOR CRLF
  267.         = {
  268.             reply(225, "ABOR command successful.");
  269.         }
  270.     |    CWD check_login CRLF
  271.         = {
  272.             if ($2)
  273.                 cwd(pw->pw_dir);
  274.         }
  275.     |    CWD check_login SP pathname CRLF
  276.         = {
  277.             if ($2 && $4 != NULL)
  278.                 cwd((char *) $4);
  279.             if ($4 != NULL)
  280.                 free((char *) $4);
  281.         }
  282.     |    HELP CRLF
  283.         = {
  284.             help((char *) 0);
  285.         }
  286.     |    HELP SP STRING CRLF
  287.         = {
  288.             help((char *) $3);
  289.         }
  290.     |    NOOP CRLF
  291.         = {
  292.             reply(200, "NOOP command successful.");
  293.         }
  294. /*
  295.  * To allow guests to put files, change check_login_guest to check_login.
  296.  */
  297.     |    XMKD check_login_guest SP pathname_perm CRLF
  298.         = {
  299.             if ($2 && $4 != NULL)
  300.                 makedir((char *) $4);
  301.             if ($4 != NULL)
  302.                 free((char *) $4);
  303.         }
  304.     |    XRMD check_login_guest SP pathname_perm CRLF
  305.         = {
  306.             if ($2 && $4 != NULL)
  307.                 removedir((char *) $4);
  308.             if ($4 != NULL)
  309.                 free((char *) $4);
  310.         }
  311.     |    XPWD check_login CRLF
  312.         = {
  313.             if ($2)
  314.                 pwd();
  315.         }
  316.     |    XCUP check_login CRLF
  317.         = {
  318.             if ($2)
  319.                 cwd("..");
  320.         }
  321.     |    STOU check_login_guest SP pathname_perm CRLF
  322.         = {
  323.             if ($2 && $4 != NULL) {
  324.                 unique++;
  325.                 store((char *) $4, "w");
  326.                 unique = 0;
  327.             }
  328.             if ($4 != NULL)
  329.                 free((char *) $4);
  330.         }
  331.     |    QUIT CRLF
  332.         = {
  333.             reply(221, "Goodbye.");
  334.             dologout(0);
  335.         }
  336.     |    error CRLF
  337.         = {
  338.             yyerrok;
  339.         }
  340.     ;
  341.  
  342. rcmd:        RNFR check_login_guest SP pathname_perm CRLF
  343.         = {
  344.             char *renamefrom();
  345.  
  346.             if ($2 && $4) {
  347.                 fromname = renamefrom((char *) $4);
  348.                 if (fromname == (char *) 0 && $4) {
  349.                     free((char *) $4);
  350.                 }
  351.             }
  352.         }
  353.     ;
  354.         
  355. username:    STRING
  356.     ;
  357.  
  358. password:    STRING
  359.     ;
  360.  
  361. byte_size:    NUMBER
  362.     ;
  363.  
  364. host_port:    NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA 
  365.         NUMBER COMMA NUMBER
  366.         = {
  367.             register char *a, *p;
  368.  
  369.             a = (char *)&data_dest.sin_addr;
  370.             a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
  371.             p = (char *)&data_dest.sin_port;
  372.             p[0] = $9; p[1] = $11;
  373.             data_dest.sin_family = AF_INET;
  374.         }
  375.     ;
  376.  
  377. form_code:    N
  378.     = {
  379.         $$ = FORM_N;
  380.     }
  381.     |    T
  382.     = {
  383.         $$ = FORM_T;
  384.     }
  385.     |    C
  386.     = {
  387.         $$ = FORM_C;
  388.     }
  389.     ;
  390.  
  391. type_code:    A
  392.     = {
  393.         cmd_type = TYPE_A;
  394.         cmd_form = FORM_N;
  395.     }
  396.     |    A SP form_code
  397.     = {
  398.         cmd_type = TYPE_A;
  399.         cmd_form = $3;
  400.     }
  401.     |    E
  402.     = {
  403.         cmd_type = TYPE_E;
  404.         cmd_form = FORM_N;
  405.     }
  406.     |    E SP form_code
  407.     = {
  408.         cmd_type = TYPE_E;
  409.         cmd_form = $3;
  410.     }
  411.     |    I
  412.     = {
  413.         cmd_type = TYPE_I;
  414.     }
  415.     |    L
  416.     = {
  417.         cmd_type = TYPE_L;
  418.         cmd_bytesz = 8;
  419.     }
  420.     |    L SP byte_size
  421.     = {
  422.         cmd_type = TYPE_L;
  423.         cmd_bytesz = $3;
  424.     }
  425.     /* this is for a bug in the BBN ftp */
  426.     |    L byte_size
  427.     = {
  428.         cmd_type = TYPE_L;
  429.         cmd_bytesz = $2;
  430.     }
  431.     ;
  432.  
  433. struct_code:    F
  434.     = {
  435.         $$ = STRU_F;
  436.     }
  437.     |    R
  438.     = {
  439.         $$ = STRU_R;
  440.     }
  441.     |    P
  442.     = {
  443.         $$ = STRU_P;
  444.     }
  445.     ;
  446.  
  447. mode_code:    S
  448.     = {
  449.         $$ = MODE_S;
  450.     }
  451.     |    B
  452.     = {
  453.         $$ = MODE_B;
  454.     }
  455.     |    C
  456.     = {
  457.         $$ = MODE_C;
  458.     }
  459.     ;
  460.  
  461. pathname:    pathstring
  462.     = {
  463.         /*
  464.          * Problem: this production is used for all pathname
  465.          * processing, but only gives a 550 error reply.
  466.          * This is a valid reply in some cases but not in others.
  467.          */
  468.         if ($1 && strncmp((char *) $1, "~", 1) == 0) {
  469.             $$ = (int)*glob((char *) $1);
  470.             if (globerr != NULL) {
  471.                 reply(550, globerr);
  472.                 $$ = NULL;
  473.             }
  474.             free((char *) $1);
  475.         } else
  476.             $$ = $1;
  477.     }
  478.     ;
  479.  
  480. pathname_perm:    pathname
  481.     = {
  482.         /*
  483.          * Check that guest's path can't access anything bad.
  484.          */
  485.         if (guest && $1 && (strstr($1,"/..") || strstr($1," ..") ||
  486.             (((char *)$1)[0]=='.'&&((char *)$1)[1]=='.'))) {
  487.             reply(550, "Sorry, guest can't use .. in paths.");
  488.             log("Bad path: %s\n", $1);
  489.             $$ = NULL;
  490.         } else {
  491.             $$ = $1;
  492.         }
  493.     }
  494.     ;
  495.  
  496. pathstring:    STRING
  497.     ;
  498.  
  499. check_login_guest:    /* empty */
  500.     = {
  501.         if (logged_in) {
  502.             $$ = 1;
  503.             if (guest) {
  504.                 reply(553, "Sorry, guest can't do that.");
  505.                 log("Bad operation\n");
  506.                 $$ = 0;
  507.             }
  508.         } else {
  509.             reply(530, "Please login with USER and PASS.");
  510.             $$ = 0;
  511.         }
  512.     }
  513.     ;
  514.  
  515. check_login:    /* empty */
  516.     = {
  517.         if (logged_in) {
  518.             $$ = 1;
  519.         } else {
  520.             reply(530, "Please login with USER and PASS.");
  521.             $$ = 0;
  522.         }
  523.     }
  524.     ;
  525.  
  526. %%
  527.  
  528. extern jmp_buf errcatch;
  529.  
  530. #define    CMD    0    /* beginning of command */
  531. #define    ARGS    1    /* expect miscellaneous arguments */
  532. #define    STR1    2    /* expect SP followed by STRING */
  533. #define    STR2    3    /* expect STRING */
  534. #define    OSTR    4    /* optional STRING */
  535.  
  536. struct tab {
  537.     char    *name;
  538.     short    token;
  539.     short    state;
  540.     short    implemented;    /* 1 if command is implemented */
  541.     char    *help;
  542. };
  543.  
  544. struct tab cmdtab[] = {        /* In order defined in RFC 765 */
  545.     { "USER", USER, STR1, 1,    "<sp> username" },
  546.     { "PASS", PASS, STR1, 1,    "<sp> password" },
  547.     { "ACCT", ACCT, STR1, 0,    "(specify account)" },
  548.     { "REIN", REIN, ARGS, 0,    "(reinitialize server state)" },
  549.     { "QUIT", QUIT, ARGS, 1,    "(terminate service)", },
  550.     { "PORT", PORT, ARGS, 1,    "<sp> b0, b1, b2, b3, b4" },
  551.     { "PASV", PASV, ARGS, 1,    "(set server in passive mode)" },
  552.     { "TYPE", TYPE, ARGS, 1,    "<sp> [ A | E | I | L ]" },
  553.     { "STRU", STRU, ARGS, 1,    "(specify file structure)" },
  554.     { "MODE", MODE, ARGS, 1,    "(specify transfer mode)" },
  555.     { "RETR", RETR, STR1, 1,    "<sp> file-name" },
  556.     { "STOR", STOR, STR1, 1,    "<sp> file-name" },
  557.     { "APPE", APPE, STR1, 1,    "<sp> file-name" },
  558.     { "MLFL", MLFL, OSTR, 0,    "(mail file)" },
  559.     { "MAIL", MAIL, OSTR, 0,    "(mail to user)" },
  560.     { "MSND", MSND, OSTR, 0,    "(mail send to terminal)" },
  561.     { "MSOM", MSOM, OSTR, 0,    "(mail send to terminal or mailbox)" },
  562.     { "MSAM", MSAM, OSTR, 0,    "(mail send to terminal and mailbox)" },
  563.     { "MRSQ", MRSQ, OSTR, 0,    "(mail recipient scheme question)" },
  564.     { "MRCP", MRCP, STR1, 0,    "(mail recipient)" },
  565.     { "ALLO", ALLO, ARGS, 1,    "allocate storage (vacuously)" },
  566.     { "REST", REST, STR1, 0,    "(restart command)" },
  567.     { "RNFR", RNFR, STR1, 1,    "<sp> file-name" },
  568.     { "RNTO", RNTO, STR1, 1,    "<sp> file-name" },
  569.     { "ABOR", ABOR, ARGS, 1,    "(abort operation)" },
  570.     { "DELE", DELE, STR1, 1,    "<sp> file-name" },
  571.     { "CWD",  CWD,  OSTR, 1,    "[ <sp> directory-name]" },
  572.     { "XCWD", CWD,    OSTR, 1,    "[ <sp> directory-name ]" },
  573.     { "LIST", LIST, OSTR, 1,    "[ <sp> path-name ]" },
  574.     { "NLST", NLST, OSTR, 1,    "[ <sp> path-name ]" },
  575.     { "SITE", SITE, STR1, 0,    "(get site parameters)" },
  576.     { "STAT", STAT, OSTR, 0,    "(get server status)" },
  577.     { "HELP", HELP, OSTR, 1,    "[ <sp> <string> ]" },
  578.     { "NOOP", NOOP, ARGS, 1,    "" },
  579.     { "MKD",  XMKD, STR1, 1,    "<sp> path-name" },
  580.     { "XMKD", XMKD, STR1, 1,    "<sp> path-name" },
  581.     { "RMD",  XRMD, STR1, 1,    "<sp> path-name" },
  582.     { "XRMD", XRMD, STR1, 1,    "<sp> path-name" },
  583.     { "PWD",  XPWD, ARGS, 1,    "(return current directory)" },
  584.     { "XPWD", XPWD, ARGS, 1,    "(return current directory)" },
  585.     { "CDUP", XCUP, ARGS, 1,    "(change to parent directory)" },
  586.     { "XCUP", XCUP, ARGS, 1,    "(change to parent directory)" },
  587.     { "STOU", STOU, STR1, 1,    "<sp> file-name" },
  588.     { NULL,   0,    0,    0,    0 }
  589. };
  590.  
  591. struct tab *
  592. lookup(cmd)
  593.     char *cmd;
  594. {
  595.     register struct tab *p;
  596.  
  597.     for (p = cmdtab; p->name != NULL; p++)
  598.         if (strcmp(cmd, p->name) == 0)
  599.             return (p);
  600.     return (0);
  601. }
  602.  
  603. #include <arpa/telnet.h>
  604.  
  605. /*
  606.  * getline - a hacked up version of fgets to ignore TELNET escape codes.
  607.  */
  608. char *
  609. getline(s, n, iop)
  610.     char *s;
  611.     register FILE *iop;
  612. {
  613.     register c;
  614.     register char *cs;
  615.  
  616.     cs = s;
  617. /* tmpline may contain saved command from urgent mode interruption */
  618.     for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
  619.         *cs++ = tmpline[c];
  620.         if (tmpline[c] == '\n') {
  621.             *cs++ = '\0';
  622.             if (debug) {
  623.                 syslog(LOG_DEBUG, "FTPD: command: %s", s);
  624.             }
  625.             tmpline[0] = '\0';
  626.             return(s);
  627.         }
  628.         if (c == 0) {
  629.             tmpline[0] = '\0';
  630.         }
  631.     }
  632.     while (--n > 0 && (c = getc(iop)) != EOF) {
  633.         c = 0377 & c;
  634.         while (c == IAC) {
  635.             switch (c = 0377 & getc(iop)) {
  636.             case WILL:
  637.             case WONT:
  638.                 c = 0377 & getc(iop);
  639.                 printf("%c%c%c", IAC, WONT, c);
  640.                 (void) fflush(stdout);
  641.                 break;
  642.             case DO:
  643.             case DONT:
  644.                 c = 0377 & getc(iop);
  645.                 printf("%c%c%c", IAC, DONT, c);
  646.                 (void) fflush(stdout);
  647.                 break;
  648.             default:
  649.                 break;
  650.             }
  651.             c = 0377 & getc(iop); /* try next character */
  652.         }
  653.         *cs++ = c;
  654.         if (c=='\n')
  655.             break;
  656.     }
  657.     if (c == EOF && cs == s)
  658.         return (NULL);
  659.     *cs++ = '\0';
  660.     if (debug) {
  661.         syslog(LOG_DEBUG, "FTPD: command: %s", s);
  662.     }
  663.     return (s);
  664. }
  665.  
  666. static int
  667. toolong()
  668. {
  669.     time_t now;
  670.     extern char *ctime();
  671.     extern time_t time();
  672.  
  673.     reply(421,
  674.       "Timeout (%d seconds): closing control connection.", timeout);
  675.     (void) time(&now);
  676.     if (logging) {
  677.         syslog(LOG_INFO,
  678.             "FTPD: User %s timed out after %d seconds at %s",
  679.             (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
  680.     }
  681.     dologout(1);
  682. }
  683.  
  684. yylex()
  685. {
  686.     static int cpos, state;
  687.     register char *cp;
  688.     register struct tab *p;
  689.     int n;
  690.     char c;
  691.  
  692.     for (;;) {
  693.         switch (state) {
  694.  
  695.         case CMD:
  696.             (void) signal(SIGALRM, toolong);
  697.             (void) alarm((unsigned) timeout);
  698.             if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
  699.                 reply(221, "You could at least say goodbye.");
  700.                 dologout(0);
  701.             }
  702.             (void) alarm(0);
  703.             if (index(cbuf, '\r')) {
  704.                 cp = index(cbuf, '\r');
  705.                 cp[0] = '\n'; cp[1] = 0;
  706.             }
  707.             if (!index(cbuf, '\n')) {
  708.                 cbuf[0] = '\n';
  709.                 cbuf[1] = 0;
  710.             }
  711.             if (index(cbuf, ' '))
  712.                 cpos = index(cbuf, ' ') - cbuf;
  713.             else
  714.                 cpos = index(cbuf, '\n') - cbuf;
  715.             if (cpos == 0) {
  716.                 cpos = 4;
  717.             }
  718.             c = cbuf[cpos];
  719.             cbuf[cpos] = '\0';
  720.             upper(cbuf);
  721.             p = lookup(cbuf);
  722.             cbuf[cpos] = c;
  723.             if (p != 0) {
  724.                 if (p->implemented == 0) {
  725.                     nack(p->name);
  726.                     longjmp(errcatch,0);
  727.                     /* NOTREACHED */
  728.                 }
  729.                 state = p->state;
  730.                 yylval = (int) p->name;
  731.                 return (p->token);
  732.             }
  733.             break;
  734.  
  735.         case OSTR:
  736.             if (cbuf[cpos] == '\n') {
  737.                 state = CMD;
  738.                 return (CRLF);
  739.             }
  740.             /* FALL THRU */
  741.  
  742.         case STR1:
  743.             if (cbuf[cpos] == ' ') {
  744.                 cpos++;
  745.                 state = STR2;
  746.                 return (SP);
  747.             }
  748.             break;
  749.  
  750.         case STR2:
  751.             cp = &cbuf[cpos];
  752.             n = strlen(cp);
  753.             cpos += n - 1;
  754.             /*
  755.              * Make sure the string is nonempty and \n terminated.
  756.              */
  757.             if (n > 1 && cbuf[cpos] == '\n') {
  758.                 cbuf[cpos] = '\0';
  759.                 yylval = copy(cp);
  760.                 cbuf[cpos] = '\n';
  761.                 state = ARGS;
  762.                 return (STRING);
  763.             }
  764.             break;
  765.  
  766.         case ARGS:
  767.             if (isdigit(cbuf[cpos])) {
  768.                 cp = &cbuf[cpos];
  769.                 while (isdigit(cbuf[++cpos]))
  770.                     ;
  771.                 c = cbuf[cpos];
  772.                 cbuf[cpos] = '\0';
  773.                 yylval = atoi(cp);
  774.                 cbuf[cpos] = c;
  775.                 return (NUMBER);
  776.             }
  777.             switch (cbuf[cpos++]) {
  778.  
  779.             case '\n':
  780.                 state = CMD;
  781.                 return (CRLF);
  782.  
  783.             case ' ':
  784.                 return (SP);
  785.  
  786.             case ',':
  787.                 return (COMMA);
  788.  
  789.             case 'A':
  790.             case 'a':
  791.                 return (A);
  792.  
  793.             case 'B':
  794.             case 'b':
  795.                 return (B);
  796.  
  797.             case 'C':
  798.             case 'c':
  799.                 return (C);
  800.  
  801.             case 'E':
  802.             case 'e':
  803.                 return (E);
  804.  
  805.             case 'F':
  806.             case 'f':
  807.                 return (F);
  808.  
  809.             case 'I':
  810.             case 'i':
  811.                 return (I);
  812.  
  813.             case 'L':
  814.             case 'l':
  815.                 return (L);
  816.  
  817.             case 'N':
  818.             case 'n':
  819.                 return (N);
  820.  
  821.             case 'P':
  822.             case 'p':
  823.                 return (P);
  824.  
  825.             case 'R':
  826.             case 'r':
  827.                 return (R);
  828.  
  829.             case 'S':
  830.             case 's':
  831.                 return (S);
  832.  
  833.             case 'T':
  834.             case 't':
  835.                 return (T);
  836.  
  837.             }
  838.             break;
  839.  
  840.         default:
  841.             fatal("Unknown state in scanner.");
  842.         }
  843.         yyerror((char *) 0);
  844.         state = CMD;
  845.         longjmp(errcatch,0);
  846.     }
  847. }
  848.  
  849. upper(s)
  850.     char *s;
  851. {
  852.     while (*s != '\0') {
  853.         if (islower(*s))
  854.             *s = toupper(*s);
  855.         s++;
  856.     }
  857. }
  858.  
  859. copy(s)
  860.     char *s;
  861. {
  862.     char *p;
  863.     extern char *malloc(), *strcpy();
  864.  
  865.     p = malloc((unsigned) strlen(s) + 1);
  866.     if (p == NULL)
  867.         fatal("Ran out of memory.");
  868.     (void) strcpy(p, s);
  869.     return ((int)p);
  870. }
  871.  
  872. help(s)
  873.     char *s;
  874. {
  875.     register struct tab *c;
  876.     register int width, NCMDS;
  877.  
  878.     width = 0, NCMDS = 0;
  879.     for (c = cmdtab; c->name != NULL; c++) {
  880.         int len = strlen(c->name) + 1;
  881.  
  882.         if (len > width)
  883.             width = len;
  884.         NCMDS++;
  885.     }
  886.     width = (width + 8) &~ 7;
  887.     if (s == 0) {
  888.         register int i, j, w;
  889.         int columns, lines;
  890.  
  891.         lreply(214,
  892.       "The following commands are recognized (* =>'s unimplemented).");
  893.         columns = 76 / width;
  894.         if (columns == 0)
  895.             columns = 1;
  896.         lines = (NCMDS + columns - 1) / columns;
  897.         for (i = 0; i < lines; i++) {
  898.             printf("   ");
  899.             for (j = 0; j < columns; j++) {
  900.                 c = cmdtab + j * lines + i;
  901.                 printf("%s%c", c->name,
  902.                     c->implemented ? ' ' : '*');
  903.                 if (c + lines >= &cmdtab[NCMDS])
  904.                     break;
  905.                 w = strlen(c->name) + 1;
  906.                 while (w < width) {
  907.                     putchar(' ');
  908.                     w++;
  909.                 }
  910.             }
  911.             printf("\r\n");
  912.         }
  913.         (void) fflush(stdout);
  914.         reply(214, "Direct comments to ftp-bugs@%s.", hostname);
  915.         return;
  916.     }
  917.     upper(s);
  918.     c = lookup(s);
  919.     if (c == (struct tab *)0) {
  920.         reply(502, "Unknown command %s.", s);
  921.         return;
  922.     }
  923.     if (c->implemented)
  924.         reply(214, "Syntax: %s %s", c->name, c->help);
  925.     else
  926.         reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help);
  927. }
  928.